﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;

namespace FluentMef
{
    public interface IExportExpression<T>
    {
        IExportExpression<T> Metadata(string name, object value);
        IExportExpression<T> Shared();
        IExportExpression<T> NonShared();
        IImportExpression<T> Use(Type type);
        IImportExpression<T> Use(T instance);
        IImportExpression<T> Use(Func<T> instance);
        IImportExpression<T> Use<TConcrete>() where TConcrete : class, T;
    }

    sealed class ExportExpression<T> : ComposablePartDefinition, IComposablePartDefinitionBuilder, IExportExpression<T>
    {
        ContainerBuilder _builder;
        Action<IComposablePartDefinitionBuilder> _updateBuilder;
        Dictionary<string, object> _metadata = new Dictionary<string,object>();
        string _contractName;
        internal Type ExportType { get; private set; }
        internal Func<T> ExportValueInitializer { get; set; }
        internal bool ExportIsInstance { get; private set; }
        ExportDefinition _cache;
        
        internal ExportExpression(ContainerBuilder builder, string contractName, string identity, Action<IComposablePartDefinitionBuilder> updateBuilder)
        {
            _updateBuilder = updateBuilder;
            _builder = builder;
            _contractName = contractName;
            _metadata["ExportTypeIdentity"] = identity;
        }

        public IExportExpression<T> Metadata(string name, object value)
        {
            _metadata[name] = value;
            return this;
        }

        //TODO: still need to handle creation policy properly...
        public IExportExpression<T> Shared()
        {
            _metadata["System.ComponentModel.Composition.CreationPolicy"] = CreationPolicy.Shared;
            return this;
        }

        public IExportExpression<T> NonShared()
        {
            _metadata["System.ComponentModel.Composition.CreationPolicy"] = CreationPolicy.NonShared;
            return this;
        }

        private void SetExportType(Type t)
        {
            ExportValueInitializer = () => (T)Activator.CreateInstance(t);
            ExportType = t;
            ExportIsInstance = false;
        }

        private void SetExportInstance(Func<T> instance)
        {
            ExportValueInitializer = instance;
            ExportType = instance.GetType();
            ExportIsInstance = true;
        }

        public IImportExpression<T> Use(Type type)
        {
            SetExportType(type);

            _updateBuilder(this);

            return new ImportExpression<T>(this, _updateBuilder);
        }

        public IImportExpression<T> Use(T instance)
        {
            SetExportInstance(() => instance);

            _updateBuilder(this);

            return new ImportExpression<T>(this, _updateBuilder);
        }

        public IImportExpression<T> Use(Func<T> instance)
        {
            SetExportInstance(instance);

            _updateBuilder(this);

            return new ImportExpression<T>(this, _updateBuilder);
        }

        public IImportExpression<T> Use<TConcrete>() where TConcrete : class, T
        {
            SetExportType(typeof(TConcrete));

            _updateBuilder(this);

            return new ImportExpression<T>(this, _updateBuilder);
        }

        ComposablePartDefinition IComposablePartDefinitionBuilder.BuildDefinition()
        {
            return this;
        }

        public override ComposablePart CreatePart()
        {
            return new FluentComposablePart<T>(ExportValueInitializer, this);
        }

        public override IEnumerable<ExportDefinition> ExportDefinitions
        {
            get
            {
                if (_cache == null)
                {
                    _cache = new ExportDefinition(_contractName, _metadata);
                }
                return new ExportDefinition[] { _cache };
            }
        }

        public override IEnumerable<ImportDefinition> ImportDefinitions
        {
            get { return new ImportDefinition[] {}; }
        }
    }
}
